﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace Luminal
{
    public sealed partial class CommandQueue : LuminalObject
    {
        #region Read Buffer
        public Event EnqueueReadBuffer<T>(MemoryObject buffer, bool blocking, T[] data, params Event[] waitList) where T : struct
        {
            return EnqueueReadBuffer<T>(buffer, blocking, 0, data, waitList);
        }

        public Event EnqueueReadBuffer<T>(MemoryObject buffer, bool blocking, int offset, T[] data, params Event[] waitList) where T : struct
        {
            if (Disposed)
                throw new ObjectDisposedException(GetType().FullName);
            if (buffer == null)
                throw new ArgumentNullException("buffer");
            if (buffer.Disposed)
                throw new ObjectDisposedException("buffer");
            if (offset < 0)
                throw new ArgumentOutOfRangeException("offset");
            if (data == null)
                throw new ArgumentNullException("data");
            if (data.Length == 0)
                throw new ArgumentException("Parameter must contain at least one element.", "data");
            if (waitList != null && waitList.Any(n => n.Disposed))
                throw new ObjectDisposedException("waitList");
            if (waitList != null && waitList.Length == 0)
                waitList = null;

            int tSize = Marshal.SizeOf(typeof(T));
            IntPtr evt = default(IntPtr);

            IntPtr[] waitListPtrs = (waitList == null || waitList.Length == 0) ? null : Util.ExtractPointers<Event>(waitList);

            Util.Pin(data, ptr =>
            {
                Util.ThrowError(
                    "Could not queue operation.",
                    clEnqueueReadBuffer(InternalPointer, buffer.InternalPointer, blocking ? 1u : 0u, offset * tSize, tSize * data.Length, ptr, waitList == null ? 0 : (uint)waitList.Length, waitListPtrs, out evt));
            });

            return Event.FromHandle(evt);
        }
        #endregion

        #region Write Buffer
        public Event EnqueueWriteBuffer<T>(MemoryObject buffer, bool blocking, T[] data, params Event[] waitList) where T : struct
        {
            return EnqueueWriteBuffer<T>(buffer, blocking, 0, data, waitList);
        }

        public Event EnqueueWriteBuffer<T>(MemoryObject buffer, bool blocking, int offset, T[] data, params Event[] waitList) where T : struct
        {
            if (Disposed)
                throw new ObjectDisposedException(GetType().FullName);
            if (buffer == null)
                throw new ArgumentNullException("buffer");
            if (buffer.Disposed)
                throw new ObjectDisposedException("buffer");
            if (offset < 0)
                throw new ArgumentOutOfRangeException("offset");
            if (data == null)
                throw new ArgumentNullException("data");
            if (data.Length == 0)
                throw new ArgumentException("Parameter must contain at least one element.", "data");
            if (waitList != null && waitList.Any(n => n.Disposed))
                throw new ObjectDisposedException("waitList");
            if (waitList != null && waitList.Length == 0)
                waitList = null;

            int tSize = Marshal.SizeOf(typeof(T));
            IntPtr evt = default(IntPtr);

            IntPtr[] waitListPtrs = (waitList == null || waitList.Length == 0) ? null : Util.ExtractPointers<Event>(waitList);

            Util.Pin(data, ptr =>
            {
                Util.ThrowError(
                    "Could not queue operation.",
                    clEnqueueWriteBuffer(InternalPointer, buffer.InternalPointer, blocking ? 1u : 0u, offset * tSize, tSize * data.Length, ptr, waitList == null ? 0 : (uint)waitList.Length, waitListPtrs, out evt));
            });

            return Event.FromHandle(evt);
        }
        #endregion

        #region Copy Buffer
        public Event EnqueueCopyBuffer<T>(MemoryObject source, MemoryObject destination, params Event[] waitList) where T : struct
        {
            return EnqueueCopyBuffer<T>(source, destination, 0, Math.Min(source.Size, destination.Size) / Marshal.SizeOf(typeof(T)), waitList);
        }

        public Event EnqueueCopyBuffer<T>(MemoryObject source, MemoryObject destination, int offset, int elements, params Event[] waitList) where T : struct
        {
            return EnqueueCopyBuffer<T>(source, destination, offset, offset, elements, waitList);
        }

        public Event EnqueueCopyBuffer<T>(MemoryObject source, MemoryObject destination, int sourceOffset, int destinationOffset, int elements, params Event[] waitList) where T : struct
        {
            if (Disposed)
                throw new ObjectDisposedException(GetType().FullName);
            if (source == null)
                throw new ArgumentNullException("source");
            if (source.Disposed)
                throw new ObjectDisposedException("source");
            if (destination == null)
                throw new ArgumentNullException("destination");
            if (destination.Disposed)
                throw new ObjectDisposedException("destination");
            if (sourceOffset < 0)
                throw new ArgumentOutOfRangeException("sourceOffset");
            if (destinationOffset < 0)
                throw new ArgumentOutOfRangeException("destinationOffset");
            if (elements <= 0)
                throw new ArgumentOutOfRangeException("elements");
            if (waitList != null && waitList.Any(n => n.Disposed))
                throw new ObjectDisposedException("waitList");
            if (waitList != null && waitList.Length == 0)
                waitList = null;

            int tSize = Marshal.SizeOf(typeof(T));
            IntPtr evt = default(IntPtr);

            IntPtr[] waitListPtrs = (waitList == null || waitList.Length == 0) ? null : Util.ExtractPointers<Event>(waitList);

            Util.ThrowError(
                "Could not queue operation.",
                clEnqueueCopyBuffer(InternalPointer, source.InternalPointer, destination.InternalPointer, sourceOffset * tSize, destinationOffset * tSize, elements * tSize, waitList == null ? 0 : (uint)waitList.Length, waitListPtrs, out evt));
        
            return Event.FromHandle(evt);
        }
        #endregion

        // TODO: Implement Image To Buffer To Copy Methods
        // TODO: Implement Buffer To Image To Copy Methods
        // TODO: Implement Buffer Map Methods
    }
}
